package com.gitee.fanx.rpc.server;

import com.gitee.fanx.rpc.RpcConst;
import org.springframework.beans.BeansException;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.lang.NonNull;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;

import java.lang.reflect.Method;
import java.util.*;

/**
 * rpc-servlet-bean 自动扫描, 并注册 RpcHandlerServlet
 * @author fanhang
 */
@Configuration("rpcServletBean")
public class RpcServletBean extends ServletRegistrationBean<RpcHandlerServlet> implements ApplicationContextAware {
	public RpcServletBean() {
		super.setUrlMappings(Collections.singleton(RpcConst.PATH));
		super.setOrder(Ordered.HIGHEST_PRECEDENCE);
	}

	@Override
	public void setApplicationContext(ApplicationContext ctx) throws BeansException {
		RpcHandlerServlet servlet = new RpcHandlerServlet();
		Map<String, Object> beanMap = ctx.getBeansWithAnnotation(RpcExport.class);
		for (Object bean : beanMap.values()) {
			RpcExport rpcExport = bean.getClass().getAnnotation(RpcExport.class);
			if (rpcExport == null) {
				continue;
			}
			SpringMappingMethodCallback methodCallback = new SpringMappingMethodCallback(bean, rpcExport.value());
			ReflectionUtils.doWithMethods(bean.getClass(), methodCallback);
			methodCallback.routeMap.forEach((k, v) -> servlet.add(rpcExport.value(), k, v));
		}
		super.setServlet(servlet);
	}

	private static class SpringMappingMethodCallback implements ReflectionUtils.MethodCallback {
		private final Map<String, Route> routeMap;
		private final Object bean;
		private final Set<Method> apiMethods;
		public SpringMappingMethodCallback(Object bean, Class<?> apiClass) {
			this.routeMap = new HashMap<>();
			this.bean = bean;
			this.apiMethods = Set.of(apiClass.getDeclaredMethods());
		}

		@Override
		public void doWith(@NonNull Method method) throws IllegalArgumentException {
			Method apiMethod = this.findApiMethod(method);
			if (apiMethod == null) {
				return;
			}
			this.routeMap.put(apiMethod.getName(), new Route(bean, method));
		}

		private Method findApiMethod(Method method) {
			List<Method> methods = this.apiMethods.stream().filter(a -> equalsMethod(a, method)).toList();
			if (methods.isEmpty()) {
				return null;
			}
			Assert.state(methods.size() == 1, "多个匹配方法: " + method);
			return methods.get(0);
		}

		private boolean equalsMethod(Method a, Method method) {
			if (!a.getName().equals(method.getName()) || !a.getReturnType().equals(method.getReturnType())) {
				return false;
			}
			return Arrays.equals(a.getParameterTypes(), method.getParameterTypes());
		}
	}
}
